﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.Linq;
using Framework.Interfaces;
using RSSNewsGrabberModule.Mapping;
using RSSNewsGrabberModule.Models;
using FieldInfo = RSSNewsGrabberModule.Mapping.FieldInfo;

namespace RSSNewsGrabberModule
{
    public class RssParser<H, C>
        where H : IDataHeader<IDataChild>, new()
        where C : IDataChild, new()
    {
        private readonly FieldsMapping _rssChannelMapping;

        private readonly bool _enableAdditionInfo;

        private readonly string _additionalInfoRoot;

        public RssParser(FieldsMapping rssChannelMapping, string additionalInfoRoot)
        {
            _rssChannelMapping = rssChannelMapping;
            _additionalInfoRoot = additionalInfoRoot;
            _enableAdditionInfo = false;
        }

        public RssParser(FieldsMapping rssChannelMapping, bool enableAdditionInfo, string additionalInfoRoot)
        {
            _rssChannelMapping = rssChannelMapping;
            _enableAdditionInfo = enableAdditionInfo;
            _additionalInfoRoot = additionalInfoRoot;
        }

        public IDataHeader<IDataChild> ParseFromXml(XDocument xmlDocument)
        {
            var root = GetRoot(_rssChannelMapping.RootNodes, xmlDocument);
            if (root == null)
                throw new ConfigurationErrorsException("Invalid mapping for current channel. RootNodes doesn't exists");

            Type headerType = typeof(H);
            Type childType = typeof(C);
            var headerPropertiesInfo = headerType.GetProperties();
            var childPropertiesInfo = childType.GetProperties();

            var headerInfo = CreateHeader(root, headerPropertiesInfo);
            var childCollection = CreateChildren(root, childPropertiesInfo);

            headerInfo.Childrens = childCollection;

            return headerInfo;
        }

        private H CreateHeader(XElement root, PropertyInfo[] headerFields)
        {
            if (root == null) throw new ArgumentNullException("root");
            var headerRoot = root.Element(_rssChannelMapping.HeaderName);
            var newHeader = new H();
            if (headerRoot != null) newHeader = CreateHeaderFromHeadElement(headerRoot, headerFields);
            else
            {
                foreach (var headerField in headerFields)
                {
                    if (_rssChannelMapping.Header.Values.Any(el => el.FieldName.Equals(headerField.Name, StringComparison.CurrentCultureIgnoreCase)))
                    {
                        var tagName = _rssChannelMapping.Header.FirstOrDefault(el =>el.Value.FieldName.Equals(headerField.Name, StringComparison.CurrentCultureIgnoreCase)).Key;
                        SetupProperty(newHeader, headerField, _rssChannelMapping.Header[tagName], tagName, root);
                    }
                }
            }
            return newHeader;
        }

        private H CreateHeaderFromHeadElement(XElement header, PropertyInfo[] headerProperties)
        {
            if (header == null) throw new ArgumentNullException("header");
            var headElement = new H();
            foreach (var xEl in header.Elements())
            {
                string nameEl = xEl.Name.ToString();
                if (_rssChannelMapping.Header.ContainsKey(nameEl))
                {
                    var fieldsMappingInfo = _rssChannelMapping.Header[nameEl];
                    var fieldsInfo = headerProperties.FirstOrDefault(fld => fld.Name.Equals(fieldsMappingInfo.FieldName,StringComparison.CurrentCultureIgnoreCase));
                    if (fieldsInfo != null)
                        SetupProperty(headElement, fieldsInfo, fieldsMappingInfo, header.Name.ToString(), header);
                    else
                        throw new ConfigurationErrorsException(
                            string.Format("Field {0} doesn't find in type {1}", fieldsMappingInfo.FieldName,
                                          typeof (H).Name));
                }
            }
            return headElement;
        }

        private ICollection<IDataChild> CreateChildren(XElement rootNode, PropertyInfo[] childProperties)
        {
            if (rootNode == null) throw new ArgumentNullException("rootNode");
            var collection = new LinkedList<IDataChild>();
            foreach (var node in rootNode.Elements())
            {
                var child = new C();
                var addItems = new AdditionalInformationRoot(_additionalInfoRoot);
                if (_rssChannelMapping.ItemName.Equals(node.Name))
                {
                    foreach (var childField in node.Elements())
                    {
                        var tagName = childField.Name.ToString();
                        var field =
                            childProperties.SingleOrDefault(
                                fld => fld.Name.Equals(_rssChannelMapping.Item[tagName]));
                        if (field != null)
                            SetupProperty(child, field, _rssChannelMapping.Item[tagName], tagName, childField);
                        else if (_enableAdditionInfo)
                            addItems.Add(childField);
                    }
                }
                else
                {
                    if (_enableAdditionInfo)
                        addItems.Add(node);
                }
                if(_enableAdditionInfo)
                {
                    child.AdditionalContentType = ContentType.Xml;
                    child.AdditionalContent = addItems.ToString();
                }
                collection.AddLast(child);
            }
            return collection;
        }

        private void SetupProperty<Obj>(Obj obj, PropertyInfo property, FieldInfo fieldInfo, string tagName,XElement root)
        {
            if (root == null) throw new ArgumentNullException("root");
            object value = null;
            switch (_rssChannelMapping.Header[tagName].Type)
            {
                case FieldType.Multi:
                    {
                        var xElement = root.Element(tagName);
                        if (xElement == null)
                            return;
                        value = root.Elements(tagName).Select(el => el.Value);
                        break;
                    }
                case FieldType.Single:
                    {
                        var xElement = root.Element(tagName);
                        if (xElement == null)
                            return;
                        value = xElement.Value;
                        break;
                    }
                case FieldType.Undefined:
                    {
                        throw new ConfigurationErrorsException(
                            string.Format("Undefined element type {0} in configuration file",
                                          _rssChannelMapping.Header[tagName].Type));
                    }
            }
            property.SetValue(obj, fieldInfo.Converter.Convert(value),null);
        }

        private XElement GetRoot(IEnumerable<string> rootList,XDocument document)
        {
            var isFirst = true;
            XElement result = document.Root;
            foreach (var item in rootList)
            {
                if (!isFirst)
                    result = result.Element(item);
                isFirst = false;
            }
            return result;
        }
    }
}
